﻿using System;
using System.Data.Objects;
using System.Data.Services;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace AstoriaPlus
{
	public class DataServiceRules<TDataService, TDataSource> : IDataServiceRules<TDataService, TDataSource>
		where TDataService : DataService<TDataSource>
		where TDataSource : class
	{
		private readonly ExtendedDataServiceConfiguration<TDataService, TDataSource> configuration;

		public DataServiceRules(ExtendedDataServiceConfiguration<TDataService, TDataSource> configuration)
		{
			this.configuration = configuration;
		}

		public IDataServiceRules<TDataService, TDataSource, TEntity> For<TEntity>(Expression<Func<TDataSource, IObjectSet<TEntity>>> entitySetProperty) where TEntity : class
		{
			var entitySetName = GetPropertyName(entitySetProperty);
			return new DataServiceRules<TDataService, TDataSource, TEntity>(this.configuration, entitySetName);
		}

		public IDataServiceRules<TDataService, TDataSource, TEntity> For<TEntity>(Expression<Func<TDataSource, IQueryable<TEntity>>> entitySetProperty) where TEntity : class
		{
			var entitySetName = GetPropertyName(entitySetProperty);
			return new DataServiceRules<TDataService, TDataSource, TEntity>(this.configuration, entitySetName);
		}

		public void SetOperationRights(Expression<Action<TDataService>> operationMethod, ServiceOperationRights rights)
		{
			var methodName = GetMethodName(operationMethod);
			this.configuration.Configuration.SetServiceOperationAccessRule(methodName, rights);
		}

		public IDataServiceRules<TDataService, TDataSource> OnChange(Action<IEntityChangeRules<TDataService, TDataSource>> changeRules)
		{
			changeRules(new EntityChangeRules<TDataService, TDataSource>(this.configuration));
			return this;
		}

		private static string GetPropertyName(LambdaExpression propertyExpression)
		{
			var property = propertyExpression.Body as MemberExpression;
			if (property == null || !(property.Member is PropertyInfo))
			{
				throw new ArgumentException(string.Format(
					CultureInfo.CurrentCulture,
					"Expression must be of the form 'x => x.PropertyName'. Invalid expression '{0}'.",
					propertyExpression), "propertyExpression");
			}

			return property.Member.Name;
		}

		private static string GetMethodName(LambdaExpression methodExpression)
		{
			if (methodExpression.Body.NodeType != ExpressionType.Call)
			{
				throw new ArgumentException(string.Format(
					CultureInfo.CurrentCulture,
					"Expression must be of the form 'x => x.OperationMethod(null, null, ...)'. Invalid expression '{0}'.",
					methodExpression), "methodExpression");
			}

			return ((MethodCallExpression)methodExpression.Body).Method.Name;
		}
	}

	public class DataServiceRules<TDataService, TDataSource, TEntity> : IDataServiceRules<TDataService, TDataSource, TEntity>
		where TDataService : DataService<TDataSource>
		where TDataSource : class
		where TEntity : class
	{
		private readonly ExtendedDataServiceConfiguration<TDataService, TDataSource> configuration;
		private readonly string entitySetName;

		public DataServiceRules(ExtendedDataServiceConfiguration<TDataService, TDataSource> configuration, string entitySetName)
		{
			this.configuration = configuration;
			this.entitySetName = entitySetName;
		}

		public ExtendedDataServiceConfiguration<TDataService, TDataSource> Configuration
		{
			get { return this.configuration; }
		}

		public IDataServiceRules<TDataService, TDataSource, TEntity> SetRights(EntitySetRights rights)
		{
			this.configuration.Configuration.SetEntitySetAccessRule(this.entitySetName, rights);
			return this;
		}

		public IDataServiceRules<TDataService, TDataSource, TEntity> OnChange(Action<IEntityChangeRules<TDataService, TDataSource, TEntity>> changeRules)
		{
			changeRules(new EntityChangeRules<TDataService, TDataSource, TEntity>(this.configuration, this.entitySetName));
			return this;
		}

		public IDataServiceRules<TDataService, TDataSource, TEntity> OnQuery(Func<IEntityQueryRules<TDataService, TDataSource, TEntity>, Expression<Func<TEntity, DataServiceQueryContext<TDataService, TDataSource>, bool>>> queryRules)
		{
			var query = queryRules(new EntityQueryRules<TDataService, TDataSource, TEntity>(this.configuration, this.entitySetName));
			this.configuration.QueryInterceptors[typeof(TEntity)] = query;
			return this;
		}
	}
}
